<?php
/**
* File holding the controller class
*
* @author       Fabrice Douteaud <clearbudget@douteaud.com>
* @package      framework
* @access       public
*/

/***********************************************************************

  Copyright (C) 2008  Fabrice douteaud(clearbudget@douteaud.com)

    This file is part of ClearBudget.

    ClearBudget is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    

    ClearBudget is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with ClearBudget.  If not, see <http://www.gnu.org/licenses/>.

************************************************************************/

/**
* Class managing the flow of event from a given request. It controls the actions
* that are requested and build the output message.
* 
* this is the applicaton controller.
* 
* @author       Fabrice Douteaud <clearbudget@douteaud.com>
* @package      framework
* @access       public
*/
class class_controller {
	/**
	* @var object Hold the instance of the current class
	* @access private
	* @static	
	*/
  private static $_instance = null;
  /**
	* @var string The text to be output
	* @access private
	* @static	
	*/
  private static $output = '';
 	/**
	* @var boolean A flag to tell if the output should be json encoded
	* @access private
	* @static	
	*/
  private static $json = false;
  
  /**
	* @var string contains the tracking code if any
	* @access private
	* @static	
	*/
  private static $trackTag = '';

  /**
	* @var array valid actions and the secure flag
	* @access private
	* @static	
	*/
  private static $actions = array(
    'login'=>0,
    'actions'=>1,
    'autoFileItem'=>1,
    'credits'=>0,
    'deleteImport'=>1,
    'doBackup'=>1,
    'editCategories'=>1,
    'editCategoriesSubmit'=>1,
    'editKeywords'=>1,
    'editKeywordsSubmit'=>1,
    'editTransactionSubmit'=>1,
    'graph'=>1,
    'home'=>1,
    'listCategoryItems'=>1,
    'listImportTransactions'=>1,
    'manageUpload'=>1,
    'search'=>1,
    'setDateRange'=>1,
    'settings'=>1,
    'setup'=>1,
    'setupSubmit'=>1,
    'tableCredits'=>1,
    'tableDebits'=>1,
    'tableOVerview'=>1,
    'uploadQif'=>1,
    'uploadQifSubmit'=>1,
    );
  
  /**  
	* log the user using the secure cookie 
	* 
	* @param string $userId the user Id
	* @return void
	* @access public
	* @static	
	*/
  public static function loginUser() {
    $username = class_settings::getUsername();
    $password = class_settings::getPassword();
    class_userCookie::setUserCookie($username, $password);
    }

  /**  
	* Validate the action given to be sure it is part of the valid one 
	* 
	* @return boolean true if valid, false otherwise
	* @access private
	* @static	
	*/
  private static function validateAction() {
    global $context;
    class_debug::addMsg(__FILE__, 'action to validate is '.$context->action, DEBUGDEBUG);
    if(isset(self::$actions[$context->action])) {
      class_debug::addMsg(__FILE__, 'action "'.$context->action.'" is valid', DEBUGINFO);
      return true;
      }
    class_debug::addMsg(__FILE__, 'action "'.$context->action.'" is invalid', DEBUGINFO);
    return false;
    }
    
  /**  
	* Check if the user should be logged in
	* 
	* If the site require to be logged in, then it checks if the user is logged in.
	* If not, redirect him to the login page.	
	*	
	* @return void
	* @access private
	* @static	
	*/
  private static function controlSiteAccess() {
    global $context;
    
    // if the request action does not require login, we go there anyway
    if(self::$actions[$context->action] === 0) {
      class_debug::addMsg(__FILE__, 'Action "'.$context->action.'" does not require to be logged in', DEBUGINFO);
      return;
      }
    
    // check if the site is to be secured
    if(class_settings::secureAccess() === '1') {    
      class_debug::addMsg(__FILE__, 'Application access is secure, checking if user is logged in', DEBUGINFO);
      
      // check if the user is logged in and page required to be logged in
      if(!$context->isUserLoggedIn()) {
        class_debug::addMsg(__FILE__, 'User is not logged, redirecting to login page', DEBUGINFO);
        // save the requested page if not present yet
        if($context->backAction == null) $context->setBackAction($context->action);
        // set the next action to be the login page
        $context->redirect('login');
        }
      else {
        class_debug::addMsg(__FILE__, 'User is logged in', DEBUGINFO);
        }
      }
    else {
      class_debug::addMsg(__FILE__, 'Application access is free', DEBUGINFO);
      }
    }
  
	/**
	* Performs the requested action from the context object 
	*
	* Multiple action file might be loaded in a loop. One action might set a next action to be called in the context.	
	*
	* @return void
	* @access public
	* @static	
	*/		
  public static function doAction() {
    global $context;
    // we always check if the application is ready first
    self::applicationReady();
    
    // validate the request action
    if(self::validateAction()==false) {
      $context->redirect('home');
      }
    
    // we always check if the application should be password protected
    self::controlSiteAccess();
    
    // load the property key object to perform translation
    $keys = class_propertyKey::getInstance();
    class_debug::addMsg(__FILE__, 'Property keys loaded', DEBUGINFO);
    
    // load the action(s) needed
    // this is a loop since an action can require a next action to be loaded
    do {
      class_debug::addMsg(__FILE__, 'Start action: '.$context->action, DEBUGINFO);
      // preset the next action to be false - prevents looping for ever :-)
      $context->nextAction = false;
      // prepare the path to be included
      $actionPath = 'actions'.DIRECTORY_SEPARATOR.$context->action.'.php';
      // if file does not exist, we simply continue
      if(file_exists($actionPath)) {
        // includes the files (i.e.: execute it)
        include($actionPath);
        class_debug::addMsg(__FILE__, 'Done loading "'.$actionPath.'"', DEBUGINFO);
        }
      else {
        class_debug::addMsg(__FILE__, 'loading of "'.$actionPath.'" failed', DEBUGINFO);
        break;
        }
      // if next action is set, we override the context action
      if($context->nextAction !== false) $context->redirect($context->nextAction);
      }
    while($context->nextAction !== false);
    
    // now load the snippet output
    // build up the path to the snippet
    $snippetPath = 'snippets'.DIRECTORY_SEPARATOR.$context->snippet.'.php';
    // check snippet existence and load it
    if(file_exists($snippetPath)) {
      ob_start();
      // load the content
      include($snippetPath);
      // get the content from the output buffer
      self::$output = ob_get_contents();
      // clean the buffer
      ob_end_clean();
      class_debug::addMsg(__FILE__, 'Done loading "'.$snippetPath.'"', DEBUGINFO);
      }
    else {
      class_debug::addMsg(__FILE__, 'loading of "'.$snippetPath.'" failed', DEBUGINFO);
      }
    }

/**
	* Performs the requested action 
	*
	* Multiple action file might be loaded in a loop. One action might set a next action to be called in the context.	
	*
	* @return the output of the action
	* @access private
	* @static	
	*/		
  private static function doRawAction($action) {
    // load the property key object to perform translation
    $keys = class_propertyKey::getInstance();
    
    // prepare the path to be included
    $actionPath = 'actions'.DIRECTORY_SEPARATOR.$action.'.php';
    // if file does not exist, we simply continue
    if(file_exists($actionPath)) {
      // includes the files (i.e.: execute it)
      include($actionPath);
      class_debug::addMsg(__FILE__, 'Raw loading of "'.$actionPath.'" done', DEBUGINFO);
      }
    else {
      class_debug::addMsg(__FILE__, 'Raw loading of "'.$actionPath.'" failed', DEBUGINFO);
      }
    
    // build up the path to the snippet
    $snippetPath = 'snippets'.DIRECTORY_SEPARATOR.$action.'.php';
    // check snippet existence and load it
    if(file_exists($snippetPath)) {
      ob_start();
      include($snippetPath);
      $output = ob_get_contents();
      ob_end_clean();
      class_debug::addMsg(__FILE__, 'Raw loading "'.$snippetPath.'" done', DEBUGINFO);
      }
    else {
      $output = '';
      class_debug::addMsg(__FILE__, 'Raw loading of "'.$snippetPath.'" failed', DEBUGINFO);
      }
    return $output;
    }
    
  /**
	* Render the output when in AJAX mode 
	*
	* Ajax mode is dictated from the context as it is a parameter in the query.
	* This function checks if the output is json encoded as to not output debug information.	
	*
	* @return void
	* @access private
	* @static	
	*/		
  private static function renderAjax() {
    // echo the output
    echo self::$output;
    }
  
  /**
	* Render the output 
	*
	* Renders the main template page.	
	*
	* @return void
	* @access private
	* @static	
	*/		
  private static function renderGeneric() {
    // load the property key object to perform translation
    $keys = class_propertyKey::getInstance();
    ob_start();
    // include the main template file
    include("style/index.php");
    // get the content
    $output =  ob_get_contents();
    // stop buffering
    ob_end_clean();
    // echo the ouput
    echo $output;
    }
    
  /**
	* Master function to render the output 
	*
	* Switch the rendering between Ajax and non Ajax calls known from the context	
	*
	* @return void
	* @access public
	* @static	
	*/		
  public static function render() {
    global $context;
    // always tell the browser to not cache
    self::sendNoCacheHeader();
      
    // if in AJAX mode, echo of the raw action ouput
    // if not, load the default template page
    if($context->ajax) {
      class_debug::addMsg(__FILE__, 'Rendering in Ajax mode', DEBUGINFO);
      self::renderAjax();
      }
    else {
      class_debug::addMsg(__FILE__, 'Rendering in full mode', DEBUGINFO);
      self::renderGeneric();
      }
    class_debug::addMsg(__FILE__, 'Rendering done', DEBUGINFO);
    if(!self::$json || !DEBUGDISPLAYMSG) class_debug::displayMsg();
    }
  
  /**
	* Builds and return the trackTag 
	*	
	*
	* @return void
	* @access private
	* @static	
	*/
  private static function getTrackTag() {
    global $context;
    // if we do not want to track, we return directly
    if(!CBTRACKING) return '';
    // if we want to track, we get the total Item count and we build the track code
    $totalItemCount = $context->totalItemCount;
    $tag = '<img src="http://clearbudget.douteaud.com/track/track.php?cbNumTransactions='.$totalItemCount.'&cbVer='.CB_VERSION.'" width="1" height="1">';
    return $tag;
    }
    
  /**
	* Checks if the application is setup 
	*	
	*
	* @return void
	* @access private
	* @static	
	*/		
  private static function applicationReady() {
    global $context;
    // check if the application has been setup correctly
    // if the page requested is the setupSubmit one then we let it go
    if(class_settings::setupDone() || $context->action == 'setupSubmit') {
      class_debug::addMsg(__FILE__, 'Application is setup', DEBUGDEBUG);
      return true;
      }
    else {
      class_debug::addMsg(__FILE__, 'Application is not setup yet', DEBUGDEBUG);
      $context->redirect('setup');
      $context->snippet = 'setup';
      return false;
      }
    return false;
    }

  /**
	* Sends the HTTP headers to force the user agent to not cache 
	*		
	* @return void
	* @access public
	* @static	
	*/
  public static function sendNoCacheHeader() {
    // cache control: no cache
    header("Cache-Control: no-cache");
    header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
    header("Expires: Sat, 1 Jan 2000 01:00:00 GMT"); // Date in the past
    }
  
  /**
	* Sends the HTTP headers to force the user agent to redirect 
	*	
	* @param string URL the URL to redirect to (can be relative or absolute)	
	* @return void
	* @access public
	* @static	
	*/
  public static function sendRedirectTo($URL = null) {
    // prevent redirecting to nothing
    if($URL === null) return;
    // send header redirect
    header("Location: ".$URL);
    die();
    }
  
  /**
	* Provide an instance of the current class 
	*	
	* Implementation of the singleton pattern
	*   		
	* @return object An instance of this class
	* @access public
	* @static	
	*/  
  public static function getInstance() {
    if(is_null(self::$_instance)) {
      self::$_instance = new self();
      }
    return self::$_instance;
    } // getInstance()

  /**
	* class constructor 
	*	
	* access to this class is private as to implement the singleton pattern
	*   	
	* @return void
	* @access private
	*/
  private function __construct() {
    } // __construct()
  }
?>